/*
 * Copyright (c) 1990, 2022, shwfed and/or its affiliates. All rights reserved.
 *
 * @date: 2022-08-24
 *
 */
package cn.junglead.common.utils.cipher;

import cn.junglead.common.utils.exception.ActiveException;
import lombok.extern.slf4j.Slf4j;

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Objects;

/**
 * 密码学AES双向对称加密工具
 *
 * @author Gary.Wang
 * @version 1.0
 */
@Slf4j
public final class CipherAesUtil extends BaseCipherUtil {

	/**
	 * 默认的安全算法
	 */
	private static final String DEFAULT_ALGORITHM = "AES/CBC/PKCS5Padding";

	/**
	 * AES 加密方法
	 * @param plaintext 明文文本
	 * @param key 加密秘钥，秘钥长度必须为32位字节，即 AES-256
	 * @param outputFormat 输出格式，可选值：1-Base64，2-Hex
	 * @return 返回加密后的结果
	 */
	public static String encrypt(String plaintext, String key, int outputFormat){
		return encrypt(DEFAULT_ALGORITHM, plaintext, key, outputFormat);
	}

	/**
	 * AES 解密方法
	 * @param cipherText 密文文本
	 * @param key 加密秘钥，秘钥长度必须为32位字节，即 AES-256
	 * @param inputFormat 输入格式，可选值：1-Base64，2-Hex
	 * @return 返回解密后的结果
	 */
	public static String decrypt(String cipherText, String key, int inputFormat){
		return decrypt(DEFAULT_ALGORITHM, cipherText, key, inputFormat);
	}

	/**
	 * 指定算法的 AES 加密方法
	 * @param algorithms 密码器算法
	 * @param plaintext 明文文本
	 * @param key 加密秘钥，秘钥长度必须为32位字节，即 AES-256
	 * @param outputFormat 输出格式，可选值：1-Base64，2-Hex
	 * @return 返回加密后的结果
	 */
	public static String encrypt(String algorithms, String plaintext, String key, int outputFormat){
		if (algorithms.contains(NOT_SUPPORT_IV)){
			throw new ActiveException(CipherAesUtil.class, "不再支持不包含iv的算法");
		}
		SecretKeySpec secretKeySpec = generateKeygen("AES", key);

		IvParameterSpec ivParameterSpec = randomIv(16);
		byte[] ivBytes = ivParameterSpec.getIV();
		byte[] encryptedData = encrypt(algorithms, plaintext.getBytes(StandardCharsets.UTF_8), secretKeySpec, ivParameterSpec);

		byte[] result = new byte[ivBytes.length + encryptedData.length];
		System.arraycopy(ivBytes, 0, result, 0, ivBytes.length);
		System.arraycopy(encryptedData, 0, result, ivBytes.length, encryptedData.length);

		return BaseCipherUtil.toPlainString(outputFormat, result);
	}

	/**
	 * 指定算法的 AES 解密方法
	 * @param algorithms 密码器算法
	 * @param cipherText 密文文本
	 * @param key 加密秘钥，秘钥长度必须为32位字节，即 AES-256
	 * @param inputFormat 输入格式，可选值：1-Base64，2-Hex
	 * @return 返回解密后的结果
	 */
	public static String decrypt(String algorithms, String cipherText, String key, int inputFormat){
		if (algorithms.contains(NOT_SUPPORT_IV)){
			throw new ActiveException(CipherAesUtil.class, "不再支持不包含iv的算法");
		}

		SecretKeySpec secretKeySpec = generateKeygen("AES", key);

		byte[] decodedData = toCipherByte(inputFormat, cipherText);
		byte[] ivBytes = new byte[16];
		byte[] encryptedContent = new byte[decodedData.length - 16];
		System.arraycopy(decodedData, 0, ivBytes, 0, 16);
		System.arraycopy(decodedData, 16, encryptedContent, 0, encryptedContent.length);
		// 构造IV和密钥，这里是误报，从逻辑上讲肯定要从输入字节中获取偏移量值
		IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);
		byte[] decryptedData = decrypt(algorithms, encryptedContent, secretKeySpec, ivParameterSpec);

		return new String(decryptedData, StandardCharsets.UTF_8);
	}

	/**
	 * AES 加密的核心方法
	 * @param algorithms 密码器算法
	 * @param input 输入的字节内容
	 * @param key 秘钥对象
	 * @param iv 偏移向量
	 * @return 返回加密后的字节数组
	 */
	private static byte[] encrypt(String algorithms, byte[] input, Key key, IvParameterSpec iv){
		try{
			Cipher cipher = Objects.nonNull(iv) ? createCipher(algorithms, Cipher.ENCRYPT_MODE, key, iv) : createCipher(algorithms, Cipher.ENCRYPT_MODE, key);
			return cipher.doFinal(input);
		} catch (Exception exception){
			log.error("AES 加密发生异常，异常原因：{}", exception.getMessage(), exception);
		}
		return new byte[]{};
	}

	/**
	 * AES 解密的核心方法
	 * @param algorithms 密码器算法
	 * @param input 输入的字节内容
	 * @param key 秘钥对象
	 * @param iv 偏移向量
	 * @return 返回解密后的字节数组
	 */
	private static byte[] decrypt(String algorithms, byte[] input, Key key, IvParameterSpec iv){
		try{
			Cipher cipher = Objects.nonNull(iv) ? createCipher(algorithms, Cipher.DECRYPT_MODE, key, iv) : createCipher(algorithms, Cipher.DECRYPT_MODE, key);
			return cipher.doFinal(input);
		} catch (Exception exception){
			log.error("AES 解密发生异常，异常原因：{}", exception.getMessage(), exception);
		}
		return new byte[]{};
	}
}
